//===- OutputFormatOptions.cpp --------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/OutputFormatOptions.h>
#include <mcld/Module.h>
#include <mcld/Support/MsgHandling.h>

namespace {

llvm::cl::opt<mcld::sys::fs::Path,
              false,
              llvm::cl::parser<mcld::sys::fs::Path> >
    ArgOutputFilename("o",
                      llvm::cl::desc("Output filename"),
                      llvm::cl::value_desc("filename"));

llvm::cl::alias AliasOutputFilename("output",
                                    llvm::cl::desc("alias for -o"),
                                    llvm::cl::aliasopt(ArgOutputFilename));

llvm::cl::opt<mcld::LinkerConfig::CodeGenType> ArgFileType(
    "filetype",
    llvm::cl::init(mcld::LinkerConfig::Exec),
    llvm::cl::desc(
        "Choose a file type\n"
        "(not all types are supported by all targets):"),
    llvm::cl::values(
        clEnumValN(mcld::LinkerConfig::Object,
                   "obj",
                   "Emit a relocatable object ('.o') file"),
        clEnumValN(mcld::LinkerConfig::DynObj,
                   "dso",
                   "Emit an dynamic shared object ('.so') file"),
        clEnumValN(mcld::LinkerConfig::Exec,
                   "exe",
                   "Emit an executable ('.exe') file"),
        clEnumValN(mcld::LinkerConfig::Binary, "bin", "Emit a binary file"),
        clEnumValN(mcld::LinkerConfig::External,
                   "null",
                   "Emit nothing for performance testing"),
        clEnumValEnd));

llvm::cl::opt<mcld::LinkerConfig::CodeGenType> ArgOFormat(
    "oformat",
    llvm::cl::value_desc("Format"),
    llvm::cl::desc("set output format"),
    llvm::cl::init(mcld::LinkerConfig::Unknown),
    llvm::cl::values(clEnumValN(mcld::LinkerConfig::Binary,
                                "binary",
                                "generate binary machine code."),
                     clEnumValEnd));

llvm::cl::opt<bool> ArgShared("shared",
                              llvm::cl::ZeroOrMore,
                              llvm::cl::desc("Create a shared library."),
                              llvm::cl::init(false));

llvm::cl::alias ArgSharedAlias("Bshareable",
                               llvm::cl::desc("alias for -shared"),
                               llvm::cl::aliasopt(ArgShared));

llvm::cl::opt<bool> ArgPIE(
    "pie",
    llvm::cl::desc("Emit a position-independent executable file"),
    llvm::cl::init(false));

llvm::cl::opt<bool> ArgRelocatable(
    "relocatable",
    llvm::cl::desc("Generate relocatable output"),
    llvm::cl::init(false));

llvm::cl::alias ArgRelocatableAlias("r",
                                    llvm::cl::desc("alias for --relocatable"),
                                    llvm::cl::aliasopt(ArgRelocatable));

llvm::cl::opt<mcld::Input::Type> ArgFormat(
    "b",
    llvm::cl::value_desc("Format"),
    llvm::cl::desc("set input format"),
    llvm::cl::init(mcld::Input::Unknown),
    llvm::cl::values(clEnumValN(mcld::Input::Binary,
                                "binary",
                                "read in binary machine code."),
                     clEnumValEnd));

llvm::cl::alias ArgFormatAlias("format",
                               llvm::cl::desc("alias for -b"),
                               llvm::cl::aliasopt(ArgFormat));

llvm::cl::opt<bool> ArgStripDebug(
    "strip-debug",
    llvm::cl::desc("Omit debugger symbol information from the output file."),
    llvm::cl::init(false));

llvm::cl::alias ArgStripDebugAlias("S",
                                   llvm::cl::desc("alias for --strip-debug"),
                                   llvm::cl::aliasopt(ArgStripDebug));

llvm::cl::opt<bool> ArgStripAll(
    "strip-all",
    llvm::cl::desc("Omit all symbol information from the output file."),
    llvm::cl::init(false));

llvm::cl::alias ArgStripAllAlias("s",
                                 llvm::cl::desc("alias for --strip-all"),
                                 llvm::cl::aliasopt(ArgStripAll));

llvm::cl::opt<bool> ArgDiscardAll("discard-all",
                                  llvm::cl::desc("Delete all local symbols."),
                                  llvm::cl::init(false));

llvm::cl::alias ArgDiscardAllAlias("x",
                                   llvm::cl::desc("alias for --discard-all"),
                                   llvm::cl::aliasopt(ArgDiscardAll));

llvm::cl::opt<bool> ArgDiscardLocals(
    "discard-locals",
    llvm::cl::desc("Delete all temporary local symbols."),
    llvm::cl::init(false));

llvm::cl::alias ArgDiscardLocalsAlias(
    "X",
    llvm::cl::desc("alias for --discard-locals"),
    llvm::cl::aliasopt(ArgDiscardLocals));

llvm::cl::opt<bool> ArgEhFrameHdr(
    "eh-frame-hdr",
    llvm::cl::ZeroOrMore,
    llvm::cl::desc(
        "Request creation of \".eh_frame_hdr\" section and\n"
        "ELF \"PT_GNU_EH_FRAME\" segment header."),
    llvm::cl::init(false));

llvm::cl::opt<bool> ArgNMagic("nmagic",
                              llvm::cl::desc("Do not page align data"),
                              llvm::cl::init(false));

llvm::cl::alias ArgNMagicAlias("n",
                               llvm::cl::desc("alias for --nmagic"),
                               llvm::cl::aliasopt(ArgNMagic));

llvm::cl::opt<bool> ArgOMagic(
    "omagic",
    llvm::cl::desc("Do not page align data, do not make text readonly"),
    llvm::cl::init(false));

llvm::cl::alias ArgOMagicAlias("N",
                               llvm::cl::desc("alias for --omagic"),
                               llvm::cl::aliasopt(ArgOMagic));

llvm::cl::opt<mcld::GeneralOptions::HashStyle> ArgHashStyle(
    "hash-style",
    llvm::cl::ZeroOrMore,
    llvm::cl::init(mcld::GeneralOptions::SystemV),
    llvm::cl::desc("Set the type of linker's hash table(s)."),
    llvm::cl::values(
        clEnumValN(mcld::GeneralOptions::SystemV,
                   "sysv",
                   "classic ELF .hash section"),
        clEnumValN(mcld::GeneralOptions::GNU,
                   "gnu",
                   "new style GNU .gnu.hash section"),
        clEnumValN(mcld::GeneralOptions::Both,
                   "both",
                   "both the classic ELF and new style GNU hash tables"),
        clEnumValEnd));

llvm::cl::opt<bool> ArgNoWarnMismatch(
    "no-warn-mismatch",
    llvm::cl::desc("Allow linking together mismatched input files."),
    llvm::cl::init(false));

// Not supported yet {
llvm::cl::opt<bool> ArgExportDynamic(
    "export-dynamic",
    llvm::cl::desc("Export all dynamic symbols"),
    llvm::cl::init(false));

llvm::cl::alias ArgExportDynamicAlias(
    "E",
    llvm::cl::desc("alias for --export-dynamic"),
    llvm::cl::aliasopt(ArgExportDynamic));

llvm::cl::opt<std::string> ArgBuildID(
    "build-id",
    llvm::cl::desc(
        "Request creation of \".note.gnu.build-id\" ELF note section."),
    llvm::cl::value_desc("style"),
    llvm::cl::ValueOptional);

llvm::cl::list<std::string> ArgExcludeLIBS(
    "exclude-libs",
    llvm::cl::CommaSeparated,
    llvm::cl::desc("Exclude libraries from automatic export"),
    llvm::cl::value_desc("lib1,lib2,..."));

// } Not supported yet

}  // anonymous namespace

using namespace mcld;

//===----------------------------------------------------------------------===//
// OutputFormatOptions
//===----------------------------------------------------------------------===//
OutputFormatOptions::OutputFormatOptions()
    : m_OutputFilename(ArgOutputFilename),
      m_FileType(ArgFileType),
      m_OFormat(ArgOFormat),
      m_Shared(ArgShared),
      m_PIE(ArgPIE),
      m_Relocatable(ArgRelocatable),
      m_Format(ArgFormat),
      m_StripDebug(ArgStripDebug),
      m_StripAll(ArgStripAll),
      m_DiscardAll(ArgDiscardAll),
      m_DiscardLocals(ArgDiscardLocals),
      m_EhFrameHdr(ArgEhFrameHdr),
      m_NMagic(ArgNMagic),
      m_OMagic(ArgOMagic),
      m_HashStyle(ArgHashStyle),
      m_ExportDynamic(ArgExportDynamic),
      m_BuildID(ArgBuildID),
      m_ExcludeLIBS(ArgExcludeLIBS),
      m_NoWarnMismatch(ArgNoWarnMismatch) {
}

bool OutputFormatOptions::parse(mcld::Module& pModule, LinkerConfig& pConfig) {
  if (!parseOutput(pModule, pConfig)) {
    mcld::unreachable(mcld::diag::unrecognized_output_file) << pModule.name();
    return false;
  }

  if (mcld::Input::Binary == m_Format)
    pConfig.options().setBinaryInput();

  pConfig.options().setStripDebug(m_StripDebug || m_StripAll);
  if (m_StripAll)
    pConfig.options().setStripSymbols(mcld::GeneralOptions::StripAllSymbols);
  else if (m_DiscardAll)
    pConfig.options().setStripSymbols(mcld::GeneralOptions::StripLocals);
  else if (m_DiscardLocals)
    pConfig.options().setStripSymbols(mcld::GeneralOptions::StripTemporaries);
  else
    pConfig.options().setStripSymbols(mcld::GeneralOptions::KeepAllSymbols);

  pConfig.options().setEhFrameHdr(m_EhFrameHdr);
  pConfig.options().setPIE(m_PIE);
  pConfig.options().setNMagic(m_NMagic);
  pConfig.options().setOMagic(m_OMagic);
  pConfig.options().setHashStyle(m_HashStyle);
  pConfig.options().setExportDynamic(m_ExportDynamic);

  // --exclude-libs
  llvm::cl::list<std::string>::iterator exclude,
      excludeEnd = m_ExcludeLIBS.end();
  for (exclude = m_ExcludeLIBS.begin(); exclude != excludeEnd; ++exclude) {
    pConfig.options().excludeLIBS().insert(*exclude);
  }

  if (m_NoWarnMismatch)
    pConfig.options().setWarnMismatch(false);
  else
    pConfig.options().setWarnMismatch(true);
  // build-id
  // exclude-libs

  return true;
}

/// configure the output filename
bool OutputFormatOptions::parseOutput(Module& pModule, LinkerConfig& pConfig) {
  if (true == m_Shared || true == m_PIE) {
    // -shared or -pie
    m_FileType = mcld::LinkerConfig::DynObj;
  } else if (true == m_Relocatable) {
    // partial linking
    m_FileType = mcld::LinkerConfig::Object;
  } else if (mcld::LinkerConfig::Binary == m_OFormat) {
    // binary output
    m_FileType = mcld::LinkerConfig::Binary;
  }

  pConfig.setCodeGenType(m_FileType);

  std::string output_filename(m_OutputFilename.native());

  if (m_OutputFilename.empty()) {
    if (llvm::Triple::Win32 == pConfig.targets().triple().getOS()) {
      output_filename.assign("_out");
      switch (m_FileType) {
        case mcld::LinkerConfig::Object: {
          output_filename += ".obj";
          break;
        }
        case mcld::LinkerConfig::DynObj: {
          output_filename += ".dll";
          break;
        }
        case mcld::LinkerConfig::Exec: {
          output_filename += ".exe";
          break;
        }
        case mcld::LinkerConfig::External:
          break;
        default: {
          return false;
          break;
        }
      }  // switch
    } else {
      if (mcld::LinkerConfig::Object == m_FileType ||
          mcld::LinkerConfig::DynObj == m_FileType ||
          mcld::LinkerConfig::Exec == m_FileType ||
          mcld::LinkerConfig::External == m_FileType) {
        output_filename.assign("a.out");
      } else {
        return false;
      }
    }
  }  // end of if empty m_OutputFilename

  pModule.setName(output_filename);
  return true;
}
